
/*
 * 6200ch - an external channel changer for Motorola DCT-6200 Tuner
 *
 * Copyright 2004,2005 by Stacey D. Son ( mythdev a son d org )
 * Copyright 2009 by Preston Crow (pc dash 6200 a crowcastle d net )
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <libavc1394/rom1394.h>
#include <libavc1394/avc1394.h>
#include <libraw1394/raw1394.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h> //for LLONG_MAX
#include <getopt.h>
#include <unistd.h> // for usleep
#include <string.h>

#define STARTING_NODE 1  /* skip 1394 nodes to avoid error msgs */
#define STARTING_PORT 0
#define RETRY_COUNT_SLOW 1
#define RETRY_COUNT_FAST 0

/*
 * Definitions of key codes
 * taken from firewiredevice.h in the MythTV source code
 */
    typedef enum
    {
        kAVCPowerOn,
        kAVCPowerOff,
        kAVCPowerUnknown,
        kAVCPowerQueryFailed,
    } PowerState;

    // AVC commands
    typedef enum
    {
        kAVCControlCommand         = 0x00,
        kAVCStatusInquiryCommand   = 0x01,
        kAVCSpecificInquiryCommand = 0x02,
        kAVCNotifyCommand          = 0x03,
        kAVCGeneralInquiryCommand  = 0x04,

        kAVCNotImplementedStatus   = 0x08,
        kAVCAcceptedStatus         = 0x09,
        kAVCRejectedStatus         = 0x0a,
        kAVCInTransitionStatus     = 0x0b,
        kAVCImplementedStatus      = 0x0c,
        kAVCChangedStatus          = 0x0d,

        kAVCInterimStatus          = 0x0f,
        kAVCResponseImplemented    = 0x0c,
    } IEEE1394Command;

    // AVC unit addresses
    typedef enum
    {
        kAVCSubunitId0                = 0x00,
        kAVCSubunitId1                = 0x01,
        kAVCSubunitId2                = 0x02,
        kAVCSubunitId3                = 0x03,
        kAVCSubunitId4                = 0x04,
        kAVCSubunitIdExtended         = 0x05,
        kAVCSubunitIdIgnore           = 0x07,

        kAVCSubunitTypeVideoMonitor   = (0x00 << 3),
        kAVCSubunitTypeAudio          = (0x01 << 3),
        kAVCSubunitTypePrinter        = (0x02 << 3),
        kAVCSubunitTypeDiscRecorder   = (0x03 << 3),
        kAVCSubunitTypeTapeRecorder   = (0x04 << 3),
        kAVCSubunitTypeTuner          = (0x05 << 3),
        kAVCSubunitTypeCA             = (0x06 << 3),
        kAVCSubunitTypeVideoCamera    = (0x07 << 3),
        kAVCSubunitTypePanel          = (0x09 << 3),
        kAVCSubunitTypeBulletinBoard  = (0x0a << 3),
        kAVCSubunitTypeCameraStorage  = (0x0b << 3),
        kAVCSubunitTypeMusic          = (0x0c << 3),
        kAVCSubunitTypeVendorUnique   = (0x1c << 3),
        kAVCSubunitTypeExtended       = (0x1e << 3),
        kAVCSubunitTypeUnit           = (0x1f << 3),
    } IEEE1394UnitAddress;

    // AVC opcode
    typedef enum
    {
        // Unit
        kAVCUnitPlugInfoOpcode               = 0x02,
        kAVCUnitDigitalOutputOpcode          = 0x10,
        kAVCUnitDigitalInputOpcode           = 0x11,
        kAVCUnitChannelUsageOpcode           = 0x12,
        kAVCUnitOutputPlugSignalFormatOpcode = 0x18,
        kAVCUnitInputPlugSignalFormatOpcode  = 0x19,
        kAVCUnitConnectAVOpcode              = 0x20,
        kAVCUnitDisconnectAVOpcode           = 0x21,
        kAVCUnitConnectionsOpcode            = 0x22,
        kAVCUnitConnectOpcode                = 0x24,
        kAVCUnitDisconnectOpcode             = 0x25,
        kAVCUnitUnitInfoOpcode               = 0x30,
        kAVCUnitSubunitInfoOpcode            = 0x31,
        kAVCUnitSignalSourceOpcode           = 0x1a,
        kAVCUnitPowerOpcode                  = 0xb2,

        // Common Unit + Subunit
        kAVCCommonOpenDescriptorOpcode       = 0x08,
        kAVCCommonReadDescriptorOpcode       = 0x09,
        kAVCCommonWriteDescriptorOpcode      = 0x0A,
        kAVCCommonSearchDescriptorOpcode     = 0x0B,
        kAVCCommonObjectNumberSelectOpcode   = 0x0D,
        kAVCCommonPowerOpcode                = 0xB2,
        kAVCCommonReserveOpcode              = 0x01,
        kAVCCommonPlugInfoOpcode             = 0x02,
        kAVCCommonVendorDependentOpcode      = 0x00,

        // Panel
        kAVCPanelPassThrough                 = 0x7c,
    } IEEE1394Opcode;

    // AVC param 0
    typedef enum
    {
        kAVCPowerStateOn           = 0x70,
        kAVCPowerStateOff          = 0x60,
        kAVCPowerStateQuery        = 0x7f,
    } IEEE1394UnitPowerParam0;


int query_power(raw1394handle_t handle, int device, int verbose);

void usage(){
   fprintf(stderr, "Usage: 6200ch [-v] [-q] "
           "[-n NODE] [-g GUID] [-p PORT] \n");
   fprintf(stderr, "-v        print additional verbose output\n");
   fprintf(stderr, "-n NODE   node to start device scanning on (default:%i)\n",
           STARTING_NODE);
   fprintf(stderr, "-p PORT   port/adapter to use              (default:%i)\n",
           STARTING_PORT);
   fprintf(stderr, "-g GUID   GUID to use, -n switch, if present, will be ignored.\n");
   fprintf(stderr, "Output:\n");
   fprintf(stderr, "stdout: on, off or blank\n");
//   fprintf(stderr, "Return Code: 0=on, 1=off, 2=error\n");
   exit(2);
}

int main (int argc, char *argv[])
{
   rom1394_directory dir;
   int device = -1;
   int i;
   int verbose = 0;
   int query_state = 1;
   int bGUID=0;
   octlet_t cli_GUID=0LL;
   octlet_t node_GUID=0LL;

   /* some people experience crashes when starting on node 1 */
   int starting_node = STARTING_NODE;
   int starting_port = STARTING_PORT;
   int c;
   int rc=99;

   opterr = 0;
   while ((c = getopt(argc, argv, "qvswme4g:n:p:d:")) != -1)
   {
       switch (c) {
       case 'v':
           verbose = 1;
           break;
       case 'n':
           starting_node = atoi(optarg);
           break;
       case 'g':
           bGUID=1;
           starting_node=0;
           cli_GUID = (octlet_t)strtoll(optarg, (char **)NULL, 16);
           break;
       case 'p':
           starting_port = atoi(optarg);
           break;
       default:
           fprintf(stderr, "incorrect command line arguments\n");
           usage();
       }
   }

   /* print out usage message if too many arguments */
   if (optind == argc-1)
   {
       usage();
   }

#ifdef RAW1394_V_0_8
   raw1394handle_t handle = raw1394_get_handle();
#else
   raw1394handle_t handle = raw1394_new_handle();
#endif

   if (!handle) {
      if (!errno) {
         fprintf(stderr, "Not Compatible!\n");
      } else {
         perror("Couldn't get 1394 handle");
         fprintf(stderr, "Is ieee1394, driver, and raw1394 loaded?\n");
      }
      exit(2);
   }

   if (raw1394_set_port(handle, starting_port) < 0) {
      perror("couldn't set port");
      raw1394_destroy_handle(handle);
      exit(2);
   }

   if (verbose)
       fprintf(stderr,"starting with node: %d\n", starting_node);

   int nc = raw1394_get_nodecount(handle);
   if (bGUID!=0) {
      if (cli_GUID==0LL || cli_GUID==LLONG_MAX || cli_GUID==LLONG_MIN) {
          fprintf(stderr, "error parsing GUID command line parameter\n");
          exit(2);
      }
   }
   for (i=starting_node; i < nc; ++i) {
      if (bGUID!=0) {
         node_GUID=rom1394_get_guid(handle, i);
#ifdef DEBUG
         fprintf(stderr,"node=%d, node_GUID=%LX, cli_GUID=%LX\n", i, node_GUID, cli_GUID);
#endif
         if (cli_GUID!=node_GUID) {
             continue;
         }
      }

      if (rom1394_get_directory(handle, i, &dir) < 0) {
         fprintf(stderr,"error reading config rom directory for node %d\n", i);
         raw1394_destroy_handle(handle);
         exit(2);
      }

      if (verbose)
         fprintf(stderr,"node %d: vendor_id = 0x%08x model_id = 0x%08x spec_id = 0x%08x software_version = 0x%08x\n",
                 i, dir.vendor_id, dir.model_id,dir.unit_spec_id,dir.unit_sw_version);

      device = i;
      break;
   }

   if (device == -1) {
        fprintf(stderr, "Could not find settop box on the 1394 bus.\n");
        raw1394_destroy_handle(handle);
        exit(2);
   }

   if (query_state)
       rc=query_power(handle, device, verbose);

   raw1394_destroy_handle(handle);
   exit(rc);
}

int query_power(raw1394handle_t handle, int device, int verbose)
{
    quadlet_t cmd[2];
    quadlet_t *response;

    cmd[0] = (kAVCStatusInquiryCommand);
    cmd[0] <<= 8;
    cmd[0] |= (kAVCSubunitTypeUnit | kAVCSubunitIdIgnore);
    cmd[0] <<= 8;
    cmd[0] |= (kAVCUnitPowerOpcode);
    cmd[0] <<= 8;
    cmd[0] |= (kAVCPowerStateQuery);
    cmd[1] = 0;

    response = avc1394_transaction_block(
        handle, device, cmd, 2, RETRY_COUNT_FAST);

    if (verbose)
    {
        fprintf(stderr,"AV/C command for power query = 0x%08X\n", cmd[0]);
        if (NULL != response)
          fprintf(stderr,"  (%x) 0x%x\n", (*response) >> 24, *response);
        else
          fprintf(stderr,"  No response\n");
    }
    switch ((*response)&0xff) {
    case kAVCPowerStateOn:
       fprintf(stderr,"Power is on\n");
       printf("on\n");
       return 0;
    case kAVCPowerStateOff:
       fprintf(stderr,"Power is off\n");
       printf("off\n");
//       return 1;
        return 0;
    default:
       fprintf(stderr,"Power is unknown\n");
       return 2;
    }
}


